package com.mm.mvc;

import com.mm.utils.BytesUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
import java.util.Map.Entry;


public class MyDispatcherServlet extends HttpServlet
{

    //application.properties配置的 key=value
    private Properties properties = new Properties();


    //application.properties配置扫描包下所有的Class 类名
    private List<String> classNames = new ArrayList<>();

    //key=object对象名，如userController，value=userController对象实例
    //key使用object对象名，不带包名，目的是方便其他类的注入自己
    private Map<String, Object> ioc = new HashMap<>();

    //key=URI请求路径， value=Method对象（Controller类中的）
    private Map<String, Method> handlerMapping = new HashMap<>();

    private Map<String, Object> controllerMap = new HashMap<>();


    @Override
    public void init(ServletConfig config) throws ServletException
    {

        //1.加载配置文件  通过web.xml文件获取到配置文件application.properties并加载
        doLoadConfig(config.getInitParameter("contextConfigLocation"));

        //2.初始化所有相关联的类,扫描用户设定的包下面所有的类
        doScanner(properties.getProperty("scanPackage"));

        //3.拿到扫描到的类,通过反射机制,实例化,并且放到ioc容器中(k-v  beanName-bean) beanName默认是首字母小写
        doInstance();

        //4.初始化HandlerMapping(将url和method对应上)
        initHandlerMapping();


    }


    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
    {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
    {
        try
        {
            //处理请求
            doDispatch(req, resp);
        } catch (Exception e)
        {
            resp.getWriter().write("500!! Server Exception");
        }

    }


    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception
    {
        if (handlerMapping.isEmpty())
        {
            return;
        }

        String url = req.getRequestURI();
        String contextPath = req.getContextPath();

        url = url.replace(contextPath, "").replaceAll("/+", "/");


        //静态文件处理 1.首页.  （优化方案，在init里，读取web.xml配置的首页文件名list，在filter中直接处理。无需dispatchServlet处理）
        if("".equals(url) || "/".equals(url))
        {
            ServletOutputStream sos = resp.getOutputStream();
            URL fileUrl = this.getClass().getClassLoader().getResource("../../index.html");

            sos.write(BytesUtils.fileToBytes(fileUrl.getFile()));
            return;
        }
        //静态文件处理 2.以html结尾的
        if(url.contains(".html"))
        {

            ServletOutputStream sos = resp.getOutputStream();
            URL fileUrl = this.getClass().getClassLoader().getResource("../../" + url);

            sos.write(BytesUtils.fileToBytes(fileUrl.getFile()));
            return;
        }


        //动态请求处理
        if (!this.handlerMapping.containsKey(url))
        {
            resp.getWriter().write("404 NOT FOUND!");
            return;
        }

        Method method = this.handlerMapping.get(url);

        //获取方法的参数列表
        Class<?>[] parameterTypes = method.getParameterTypes();

        //获取请求的参数
        Map<String, String[]> parameterMap = req.getParameterMap();

        //保存参数值
        Object[] paramValues = new Object[parameterTypes.length];

        //方法的参数列表
        for (int i = 0; i < parameterTypes.length; i++)
        {
            //根据参数名称，做某些处理
            String requestParam = parameterTypes[i].getSimpleName();


            if (requestParam.equals("HttpServletRequest"))
            {
                //参数类型已明确，这边强转类型
                paramValues[i] = req;
                continue;
            }
            if (requestParam.equals("HttpServletResponse"))
            {
                paramValues[i] = resp;
                continue;
            }
            if (requestParam.equals("String"))
            {
                for (Entry<String, String[]> param : parameterMap.entrySet())
                {
                    String value = Arrays.toString(param.getValue()).replaceAll("\\[|\\]", "").replaceAll(",\\s", ",");
                    paramValues[i] = value;
                }
            }
        }
        //利用反射机制来调用
        try
        {
            method.invoke(this.controllerMap.get(url), paramValues);//第一个参数是method所对应的实例 在ioc容器中
        } catch (Exception e)
        {
            e.printStackTrace();
        }
    }


    private void doLoadConfig(String location)
    {
        //把web.xml中的contextConfigLocation对应value值的文件加载到流里面
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(location);
        try
        {
            //用Properties文件加载文件里的内容
            properties.load(resourceAsStream);
        } catch (IOException e)
        {
            e.printStackTrace();
        } finally
        {
            //关流
            if (null != resourceAsStream)
            {
                try
                {
                    resourceAsStream.close();
                } catch (IOException e)
                {
                    e.printStackTrace();
                }
            }
        }

    }

    private void doScanner(String packageName)
    {
        //把所有的.替换成/
        URL url = this.getClass().getClassLoader().getResource("/" + packageName.replaceAll("\\.", "/"));
        File dir = new File(url.getFile());
        for (File file : dir.listFiles())
        {
            if (file.isDirectory())
            {
                //递归读取包
                doScanner(packageName + "." + file.getName());
            } else
            {
                String className = packageName + "." + file.getName().replace(".class", "");
                classNames.add(className);
            }
        }
    }


    private void doInstance()
    {
        if (classNames.isEmpty())
        {
            return;
        }
        for (String className : classNames)
        {
            try
            {
                //把类搞出来,反射来实例化(只有加@MyController需要实例化)
                Class<?> clazz = Class.forName(className);
                if (clazz.isAnnotationPresent(MyController.class))
                {
                    System.out.println("ioc key:" + toLowerFirstWord(clazz.getSimpleName()));
                    ioc.put(toLowerFirstWord(clazz.getSimpleName()), clazz.newInstance());
                } else
                {
                    continue;
                }


            } catch (Exception e)
            {
                e.printStackTrace();
                continue;
            }
        }
    }


    private void initHandlerMapping()
    {
        if (ioc.isEmpty())
        {
            return;
        }
        try
        {
            for (Entry<String, Object> entry : ioc.entrySet())
            {
                Class<? extends Object> clazz = entry.getValue().getClass();
                if (!clazz.isAnnotationPresent(MyController.class))
                {
                    continue;
                }

                //拼url时,是controller头的url拼上方法上的url
                String baseUrl = "";
                if (clazz.isAnnotationPresent(MyController.class))
                {
                    MyController annotation = clazz.getAnnotation(MyController.class);
                    baseUrl = annotation.value();
                }
                Method[] methods = clazz.getMethods();
                for (Method method : methods)
                {
                    if (!method.isAnnotationPresent(MyRequestMapping.class))
                    {
                        continue;
                    }
                    MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class);
                    String url = annotation.value();

                    url = (baseUrl + "/" + url).replaceAll("/+", "/");
                    handlerMapping.put(url, method);
                    controllerMap.put(url, clazz.newInstance());
                    System.out.println("---------HandlerMapping:" + url + "," + method);
                }

            }

        } catch (Exception e)
        {
            e.printStackTrace();
        }

    }


    /**
     * 把字符串的首字母小写
     *
     * @param name
     * @return
     */
    private String toLowerFirstWord(String name)
    {
        char[] charArray = name.toCharArray();
        charArray[0] += 32;
        return String.valueOf(charArray);
    }


}
